/**
 * Class: FourierWaveform
 *
 * Purpose: Fourier signal generator
 *
 * Created: Feb 22, 2010 @ 9:38:00 AM
 * @author Ken
 */
package org.openfantasia.util.math.algorithm;

import org.openfantasia.util.math.PMath;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Create an instance, add terms (Sine or Cosine), and then call 'getValue(time)' to get the
 * instantaneous value of the signal implied by the terms.
 */
public class FourierWaveform {

	private List<Term> terms = new ArrayList<Term>();


	public FourierWaveform() {
	}


	public FourierWaveform(Term... terms) {
		this.terms.addAll(Arrays.asList(terms));
	}


	public FourierWaveform(List<Term> terms) {
		this.terms.addAll(terms);
	}


	public void add(Term term) {
		if (!this.terms.contains(term)) {
			this.terms.add(term);
		}
	}


	public void remove(Term term) {
		this.terms.remove(term);
	}


	public double getValue(double time) {
		double result = 0;
		for (Term dsc : terms) {
			result += dsc.getValue(time);
		}
		return result;
	}


	public abstract class Term {

		protected double cps = 0;

		protected double mag = 0;

		protected double phase = 0;


		public Term(double cps, double mag, double phase) {
			this.cps = cps;
			this.mag = mag;
			this.phase = phase;
		}


		public double getValue(double time) {
			double theta = (time * PMath.TWO_PI * cps) + phase;
			return mag * getFunctionValue(theta);
		}


		public abstract double getFunctionValue(double theta);


		public double getCps() {
			return cps;
		}


		public void setCps(double cps) {
			this.cps = cps;
		}


		public double getMag() {
			return mag;
		}


		public void setMag(double mag) {
			this.mag = mag;
		}


		public double getPhase() {
			return phase;
		}


		public void setPhase(double phase) {
			this.phase = phase;
		}


		@Override
		public int hashCode() {
			int result;
			long temp;
			temp = cps != +0.0d ? Double.doubleToLongBits(cps) : 0L;
			result = (int) (temp ^ (temp >>> 32));
			temp = mag != +0.0d ? Double.doubleToLongBits(mag) : 0L;
			result = 31 * result + (int) (temp ^ (temp >>> 32));
			temp = phase != +0.0d ? Double.doubleToLongBits(phase) : 0L;
			result = 31 * result + (int) (temp ^ (temp >>> 32));
			return result;
		}
	}

	public class Sine extends Term {

		public Sine(double hz, double mag, double phase) {
			super(hz, mag, phase);
		}


		public double getFunctionValue(double theta) {
			return PMath.sin(theta);
		}


		@Override
		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if (!(o instanceof Sine)) {
				return false;
			}

			Sine term = (Sine) o;

			if (Double.compare(term.cps, cps) != 0) {
				return false;
			}
			if (Double.compare(term.mag, mag) != 0) {
				return false;
			}
			if (Double.compare(term.phase, phase) != 0) {
				return false;
			}

			return true;
		}
	}

	public class Cosine extends Term {

		public Cosine(double hz, double mag, double phase) {
			super(hz, mag, phase);
		}


		public double getFunctionValue(double theta) {
			return PMath.cos(theta);
		}


		@Override
		public boolean equals(Object o) {
			if (this == o) {
				return true;
			}
			if (!(o instanceof Cosine)) {
				return false;
			}

			Cosine term = (Cosine) o;

			if (Double.compare(term.cps, cps) != 0) {
				return false;
			}
			if (Double.compare(term.mag, mag) != 0) {
				return false;
			}
			if (Double.compare(term.phase, phase) != 0) {
				return false;
			}

			return true;
		}
	}
}
